﻿using Common.FrontEnd;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Util.Props;

namespace FrontEnd
{
    /**
    /// FrontEnd is a wrapper class for the chain of front end processors. It provides methods for manipulating and
    /// navigating the processors.
    /// <p/>
    /// The front end is modeled as a series of data processors, each of which performs a specific signal processing
    /// function. For example, a processor performs Fast-Fourier Transform (FFT) on input data, another processor performs
    /// high-pass filtering. Figure 1 below describes how the front end looks like:
    /// <p/>
    /// <center> <img src="doc-files/frontend.jpg"> <br><b>Figure 1: The Sphinx4 front end.</b> </center>
    /// <p/>
    /// Each such data processor implements the {@link edu.cmu.sphinx.frontend.DataProcessor} interface. Objects that
    /// implements the {@link edu.cmu.sphinx.frontend.Data} interface enters and exits the front end, and go between the
    /// processors in the front end. The input data to the front end is typically audio data, but this front end allows any
    /// input type. Similarly, the output data is typically features, but this front end allows any output type. You can
    /// configure the front end to accept any input type and return any output type. We will describe the configuration of
    /// the front end in more detail below.
    /// <p/>
    /// <b>The Pull Model of the Front End</b>
    /// <p/>
    /// The front end uses a pull model. To obtain output from the front end, one would call the method:
    /// <p/>
    /// <code> FrontEnd frontend = ... // see how to obtain the front end below <br>Data output = frontend.getData();
    /// </code>
    /// <p/>
    /// Calling {@link #getData() getData} on the front end would in turn call the getData() method on the last
    /// DataProcessor, which in turn calls the getData() method on the second last DataProcessor, and so on, until the
    /// getData() method on the first DataProcessor is called, which reads Data objects from the input. The input to the
    /// front end is actually another DataProcessor, and is usually (though not necessarily) part of the front end and is not
    /// shown in the figure above. If you want to maintain some control of the input DataProcessor, you can create it
    /// separately, and use the {@link #setDataSource(edu.cmu.sphinx.frontend.DataProcessor) setDataSource} method to set it
    /// as the input DataProcessor. In that case, the input DataProcessor will be prepended to the existing chain of
    /// DataProcessors. One common input DataProcessor is the {@link edu.cmu.sphinx.frontend.util.Microphone}, which
    /// implements the DataProcessor interface.
    /// <p/>
    /// <code> DataProcessor microphone = new Microphone(); <br>microphone.initialize(...);
    /// <br>frontend.setDataSource(microphone); </code>
    /// <p/>
    /// Another common input DataProcessor is the {@link edu.cmu.sphinx.frontend.util.StreamDataSource}. It turns a Java
    /// {@link java.io.InputStream} into Data objects. It is usually used in batch mode decoding.
    /// <p/>
    /// <b>Configuring the front end</b>
    /// <p/>
    /// The front end must be configured through the Sphinx properties file. For details about configuring the front end,
    /// refer to the document <a href="doc-files/FrontEndConfiguration.html">Configuring the Front End</a>.
    /// <p/>
    /// Current state-of-the-art front ends generate features that contain Mel-frequency cepstral coefficients (MFCC). To
    /// specify such a front end (called a 'pipeline') in Sphinx-4, insert the following lines in the Sphinx-4 configuration
    /// file:
    /// <p/>
    /// <pre>
    /// &lt;component name="mfcFrontEnd" type="edu.cmu.sphinx.frontend.FrontEnd"&gt;
    ///     &lt;propertylist name="pipeline"&gt;
    ///        &lt;item&gt;preemphasizer&lt;/item&gt;
    ///        &lt;item&gt;windower&lt;/item&gt;
    ///        &lt;item&gt;dft&lt;/item&gt;
    ///        &lt;item&gt;melFilterBank&lt;/item&gt;
    ///        &lt;item&gt;dct&lt;/item&gt;
    ///        &lt;item&gt;batchCMN&lt;/item&gt;
    ///        &lt;item&gt;featureExtractor&lt;/item&gt;
    ///     &lt;/propertylist&gt;
    /// &lt;/component&gt;
    /// <p/>
    /// &lt;component name="preemphasizer" type="{@link edu.cmu.sphinx.frontend.filter.Preemphasizer
    /// edu.cmu.sphinx.frontend.filter.Preemphasizer}"/&gt;
    /// &lt;component name="windower" type="{@link edu.cmu.sphinx.frontend.window.RaisedCosineWindower
    /// edu.cmu.sphinx.frontend.window.RaisedCosineWindower}"/&gt;
    /// &lt;component name="dft" type="{@link edu.cmu.sphinx.frontend.transform.DiscreteFourierTransform
    /// edu.cmu.sphinx.frontend.transform.DiscreteFourierTransform}"/&gt;
    /// &lt;component name="melFilterBank" type="{@link edu.cmu.sphinx.frontend.frequencywarp.MelFrequencyFilterBank2
    /// edu.cmu.sphinx.frontend.frequencywarp.MelFrequencyFilterBank}"/&gt;
    /// &lt;component name="dct" type="{@link edu.cmu.sphinx.frontend.transform.DiscreteCosineTransform
    /// edu.cmu.sphinx.frontend.transform.DiscreteCosineTransform}"/&gt;
    /// &lt;component name="batchCMN" type="{@link edu.cmu.sphinx.frontend.feature.BatchCMN
    /// edu.cmu.sphinx.frontend.feature.BatchCMN}"/&gt;
    /// &lt;component name="featureExtractor" type="{@link edu.cmu.sphinx.frontend.feature.DeltasFeatureExtractor
    /// edu.cmu.sphinx.frontend.feature.DeltasFeatureExtractor}"/&gt;
    /// </pre>
    /// <p/>
    /// Note: In this example, 'mfcFrontEnd' becomes the name of the front end.
    /// <p/>
    /// Sphinx-4 also allows you to: <ul> <li>specify multiple front end pipelines</li> <li>specify multiple instance of the
    /// same DataProcessor in the same pipeline</li> </ul>
    /// <p/>
    /// For details on how to do this, refer to the document <a href="doc-files/FrontEndConfiguration.html">Configuring the
    /// Front End</a>.
    /// <p/>
    /// <b>Obtaining a Front End</b>
    /// <p/>
    /// In order to obtain a front end, it must be specified in the configuration file. The Sphinx-4 front end is connected
    /// to the rest of the system via the scorer. We will continue with the above example to show how the scorer will obtain
    /// the front end. In the configuration file, the scorer should be specified as follows:
    /// <p/>
    /// <pre>
    /// &lt;component name="scorer" type="edu.cmu.sphinx.decoder.scorer.SimpleAcousticScorer"&gt;
    ///     &lt;property name="frontend" value="mfcFrontEnd"/&gt;
    /// &lt;/component&gt;
    /// </pre>
    /// <p/>
    /// In the SimpleAcousticScorer, the front end is obtained in the {@link edu.cmu.sphinx.util.props.Configurable#newProperties
    /// newProperties} method as follows:
    /// <pre>
    /// public void newProperties(PropertySheet ps) throws PropertyException {
    ///     FrontEnd frontend = (FrontEnd) ps.getComponent("frontend", FrontEnd.class);
    /// }
    /// </pre>
     */
    public class FrontEnd: BaseDataProcessor
    {
        /// <summary>
        /// the name of the property list of all the components of the frontend pipe line 
        /// </summary>
        [S4ComponentList(type = typeof(IDataProcessor))]
        public static String PROP_PIPELINE = "pipeline";


        // ----------------------------
        // Configuration data
        // -----------------------------
        private List<IDataProcessor> frontEndList;

        private IDataProcessor first;
        private IDataProcessor last;
        private List<ISignalListener> signalListeners = new List<ISignalListener>();

        public FrontEnd(List<IDataProcessor> frontEndList) 
        {
            this.frontEndList = frontEndList;
            init();
        }

        public FrontEnd() {

        }

        /// <summary>
        /// @see Configurable#newProperties(sphincs.util.props.PropertySheet)
        /// </summary>
        /// <param name="ps"></param>
        override
        public void newProperties(PropertySheet ps)
        {
            base.newProperties(ps);
            frontEndList = ps.getComponentList<IDataProcessor>(PROP_PIPELINE);
            init();
        }

        private void init() 
        {
            last = null;
            foreach (IDataProcessor dp in frontEndList) 
            {
                Trace.Assert(dp != null);

                if (last != null)
                    dp.setPredecessor(last);

                if (first == null) {
                    first = dp;
                }
                last = dp;
            }
            initialize();
        }

        /// <summary>
        /// @see Sphincs.FrontEnd.DataProcessor#initialize(Sphincs.FrontEnd.CommonConfig)
        /// </summary>
        override public void initialize() 
        {
            base.initialize();
            foreach (IDataProcessor dp in frontEndList) 
            {
                dp.initialize();
            }
        }


        /**
        /// Sets the source of data for this front end. It basically sets the predecessor of the first DataProcessor of this
        /// front end.
         *
        /// @param dataSource the source of data
         */
        public void setDataSource(IDataProcessor dataSource) 
        {
            first.setPredecessor(dataSource);
        }


        /** Returns the collection of <code>DataProcessor</code>s which setup this <code>FrontEnd</code>.
         */
        public List<IDataProcessor> getElements() 
        {
            return frontEndList;
        }

        /// <summary>
        /// Returns the processed Data output, basically calls <code>getData()</code> on the last processor.
        /// </summary>
        /// <returns>an Data object that has been processed by this front end</returns>
        override public IData getData()
        {
            IData data = last.getData();

            // fire the signal listeners if its a signal
            if (data is Signal) 
            {
                fireSignalListeners((Signal) data);
            }

            return data;
        }


        /**
        /// Sets the source of data for this front end. It basically calls <code>setDataSource(dataSource)</code>.
         *
        /// @param dataSource the source of data
         */
        public void setPredecessor(IDataProcessor dataSource) 
        {
            setDataSource(dataSource);
        }


        /**
        /// Add a listener to be called when a signal is detected.
         *
        /// @param listener the listener to be added
         */
        public void addSignalListener(ISignalListener listener) 
        {
            signalListeners.Add(listener);
        }


        /**
        /// Removes a listener for signals.
         *
        /// @param listener the listener to be removed
         */
        public void removeSignalListener(ISignalListener listener) 
        {
            signalListeners.Remove(listener);
        }


        /**
        /// Fire all listeners for signals.
         *
        /// @param signal the signal that occurred
         */
        protected void fireSignalListeners(Signal signal) 
        {
            foreach (ISignalListener listener in signalListeners)
                listener.signalOccurred(signal);
        }


        /** Returns the last data processor within the <code>DataProcessor</code> chain of this <code>FrontEnd</code>.
         */
        public IDataProcessor getLastDataProcessor() {
            return last;
        }


        /**
        /// Returns a description of this FrontEnd in the format: <front end name> {<DataProcessor1>, <DataProcessor2> ...
        /// <DataProcessorN>}
         *
        /// @return a description of this FrontEnd
         */
        override
        public String ToString() 
        {
            if (last == null)
                return base.ToString() + " {}";
            LinkedList<IDataProcessor> list = new LinkedList<IDataProcessor>();
            for (IDataProcessor current = last; current != null; current = current.getPredecessor())
                list.AddFirst(current); // add processors in their correct order
            StringBuilder description = new StringBuilder(base.ToString()).Append(" {");
            foreach (IDataProcessor dp in list)
                description.Append(dp).Append(", ");
            description.Length = (description.Length - 2);
            return description.Append('}').ToString();
        }
    }
}
